import PROTOCOL_CONFIG from "../config/protocolConfig";

const MSG_TYPE_MAP = {
    2: 'Audio',
    3: 'Video',
    7: 'CmdControl',
    8: 'HeartBeat',
    9: 'Orientation',
    14: 'KeyboardInput',
    21: 'Camera',
    22: 'MicroPhone',
    60: 'PhoneControl'
};
const INVALID_MSG_TYPE = 'Invalid';

const STREAM_DELIMITER_MAGIC_WORD = 0x5A5A;
const MSG_HEADER_LENGTH = 8;

const KEY_COMMAND = "command";
const KEY_MEDIA_CONFIG = "media_config";

class MessageHandler {
    constructor(sendMessage, options) {
        this.sendMessage = sendMessage;
        this.options = options;
    }

    sendStartCmdControl(startParams) {
        const mediaConfig = startParams.media_config;
        if (mediaConfig.encode_type !== 0) {
            delete mediaConfig.remote_scheduling_elb_ip;
            delete mediaConfig.remote_scheduling_elb_port;
        }
        startParams.media_config = this._objToString(mediaConfig, ':');

        const parametersMap = new Map();
        parametersMap.set(KEY_COMMAND, PROTOCOL_CONFIG.CMD_TYPE.START);
        for (let key in startParams) {
            parametersMap.set(key, startParams[key]);
        }
        this._sendCommandMsgData(PROTOCOL_CONFIG.MSG_TYPE.CMD_CONTROL, parametersMap);
    }

    sendStopCmdControl() {
        this._sendNormalCmdControl(PROTOCOL_CONFIG.CMD_TYPE.STOP)
    }

    sendPauseCmdControl() {
        this._sendNormalCmdControl(PROTOCOL_CONFIG.CMD_TYPE.PAUSE);
    }

    sendResumeCmdControl() {
        this._sendNormalCmdControl(PROTOCOL_CONFIG.CMD_TYPE.RESUME);
    }

    _sendNormalCmdControl(cmdType) {
        const parametersMap = new Map();
        parametersMap.set(KEY_COMMAND, cmdType);
        this._sendCommandMsgData(PROTOCOL_CONFIG.MSG_TYPE.CMD_CONTROL, parametersMap);
    }

    sendHeartBeat() {
        const parametersMap = new Map();
        parametersMap.set(KEY_COMMAND, PROTOCOL_CONFIG.CMD_TYPE.HEART_BEAT);
        this._sendCommandMsgData(PROTOCOL_CONFIG.MSG_TYPE.HEART_BEAT, parametersMap);
    }

    sendMediaConfig(mediaConfig) {
        const mediaConfigStr = this._parametersToString(mediaConfig, ':');
        const parametersMap = new Map();
        parametersMap.set(KEY_COMMAND, PROTOCOL_CONFIG.CMD_TYPE.SET_MEDIA_CONFIG);
        parametersMap.set(KEY_MEDIA_CONFIG, mediaConfigStr);
        this._sendCommandMsgData(PROTOCOL_CONFIG.MSG_TYPE.CMD_CONTROL, parametersMap);
    }

    sendTouchMsgData(touchData) {
        const msgBody = new Uint8Array(17);
        msgBody[0] = touchData.id;
        msgBody[1] = touchData.action;
        msgBody[2] = (touchData.x >> 8) & 0xFF;
        msgBody[3] = touchData.x & 0xFF;
        msgBody[4] = (touchData.y >> 8) & 0xFF;
        msgBody[5] = touchData.y & 0xFF;
        msgBody[6] = (touchData.pressure >> 8) & 0xFF;
        msgBody[7] = touchData.pressure & 0xFF;
        msgBody[8] = (touchData.time & 0xFF000000) >> 24;
        msgBody[9] = (touchData.time & 0xFF000000) >> 16;
        msgBody[10] = (touchData.time & 0xFF000000) >> 8;
        msgBody[11] = touchData.time & 0xFF000000;
        msgBody[12] = touchData.orientation;
        msgBody[13] = (touchData.height >> 8) & 0xFF;
        msgBody[14] = touchData.height & 0xFF;
        msgBody[15] = (touchData.width >> 8) & 0xFF;
        msgBody[16] = touchData.width & 0xFF;
        this._sendMsgData(PROTOCOL_CONFIG.MSG_TYPE.TOUCH, msgBody, 17);
    }

    _sendCommandMsgData(msgType, parametersMap) {
        const msgBody = this._makeCommand(parametersMap);
        this._sendMsgData(msgType, msgBody, msgBody.length);
    }

    _sendMsgData(msgType, msgBody, bodyLen) {
        const len = MSG_HEADER_LENGTH + bodyLen;
        const msgData = new Uint8Array(len);
        msgData[0] = 0x5A;
        msgData[1] = 0x5A;
        msgData[2] = this._generateChecksum(msgType);
        msgData[3] = msgType;
        msgData[4] = ((bodyLen >> 24) & 0xFF);
        msgData[5] = ((bodyLen >> 16) & 0xFF);
        msgData[6] = ((bodyLen >> 8) & 0xFF);
        msgData[7] = ((bodyLen) & 0xFF);
        msgData.set(msgBody, 8);
        this.sendMessage(msgData);
    }

    _generateChecksum(msgType) {
        return ((msgType + ((STREAM_DELIMITER_MAGIC_WORD >> 8) & 0xFF) + (STREAM_DELIMITER_MAGIC_WORD & 0xFF)) & 0xFF);
    }

    _makeCommand(parametersMap, separator = '&') {
        return new TextEncoder().encode(this._parametersToString(parametersMap, separator));
    }

    _objToString(obj, separator = '&') {
        let objStr = '';
        let count = 0;
        for (let key in obj) {
            count++;
            objStr += `${key}=${obj[key]}`;
            if (count !== objStr.length) {
                objStr += separator;
            }
        }
        return objStr;
    }

    _parametersToString(parametersMap, separator = '&') {
        let parametersStr = '';
        let count = 0;
        parametersMap.forEach((value, key) => {
            count++;
            parametersStr += `${key}=${value}`
            if (count !== parametersMap.size) {
                parametersStr += separator;
            }
        });
        return parametersStr;
    }

    dealReceiveMsg(receiveMsg, callback) {
        const msgType = this._getMsgTypeFromReceiveMsg(receiveMsg);
        if (msgType === INVALID_MSG_TYPE) {
            return;
        }

        const msgBody = receiveMsg.slice(MSG_HEADER_LENGTH);

        const msgData = new TextEncoder().encode(msgBody);

        switch (msgType) {
            case MSG_TYPE_MAP["7"]:
                // cmd control
                this._processCmdControlResp(msgData, callback.cmdControlCallback);
                break;
            case MSG_TYPE_MAP["8"]:
                // heart beat
                this._processHeartBeatResp(msgData, callback.heartBeatCallback);
                break;
            case MSG_TYPE_MAP["9"]:
                // orientation
                this._processOrientation(msgData, callback.orientationCallback);
                break;
            case MSG_TYPE_MAP["14"]:
                // KeyboardInput
                this._processKeyboardInput(msgData, callback.keyboardInputCallback);
                break;
            case MSG_TYPE_MAP["21"]:
                // Camera
                this._processCamera(msgData, callback.cameraCallback);
                break;
            case MSG_TYPE_MAP["22"]:
                // MicroPhone
                this._processMicrophone(msgData, callback.microphoneCallback);
                break;
            case MSG_TYPE_MAP["60"]:
                // PhoneControl
                this._processPhoneControl(msgData, callback.phoneControlCallback);
                break;
        }
    }

    _processCmdControlResp(msg, cmdControlCallback) {
        if (!msg) {
            return;
        }
        let buf = new Uint8Array(msg);
        let text = '';
        buf.forEach(c => {
            text += String.fromCharCode(c);
        });
        let resp = this._params2JSON(text);
        let code = (Number(resp.code) & 0xFF00) >> 8;
        cmdControlCallback && cmdControlCallback(code);
    }

    _processHeartBeatResp(msg, heartBeatCallback) {
        heartBeatCallback && heartBeatCallback(msg);
    }

    _processOrientation(msg, orientationCallback) {
        const body = msg & 0xFF;
        const orientation = PROTOCOL_CONFIG.ORIENTATION[body];
        orientationCallback && orientationCallback(orientation);
    }

    _processKeyboardInput(msg, keyboardInputCallback) {
        keyboardInputCallback && keyboardInputCallback(msg);
    }

    _processCamera(msg, cameraCallback) {
        cameraCallback && cameraCallback(msg);
    }

    _processMicrophone(msg, microphoneCallback) {
        microphoneCallback && microphoneCallback(msg);
    }

    _processPhoneControl(msg, phoneControlCallback) {
        phoneControlCallback && phoneControlCallback(msg);
    }

    _params2JSON(params = '') {
        let json = {};
        params.split('&').forEach(kv => {
            let [key, val] = kv.split('=');
            json[key] = val;
        });
        return json;
    }

    _getMsgTypeFromReceiveMsg(receiveMsg) {
        if (!receiveMsg || receiveMsg.length < MSG_HEADER_LENGTH) {
            return INVALID_MSG_TYPE;
        }
        return MSG_TYPE_MAP[receiveMsg.charCodeAt(3)] || INVALID_MSG_TYPE;
    }
}

export default MessageHandler;